/*******************************************************************************
 * Copyright (c) 2010-2011 Robert Fraser                                       *
 *                                                                             *
 * Permission is hereby granted, free of charge, to any person obtaining a     *
 * copy of this software and associated documentation files (the "Software"),  *
 * to deal in the Software without restriction, including without limitation   *
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,    *
 * and/or sell copies of the Software, and to permit persons to whom the       *
 * Software is furnished to do so, subject to the following conditions:        *
 *                                                                             *
 *   - The above copyright notice and this permission notice shall be included *
 *     in all copies or substantial portions of the Software.                  *
 *                                                                             *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER      *
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING     *
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER         *
 * DEALINGS IN THE SOFTWARE.                                                   *
 *******************************************************************************/

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using zeal.core;
using zeal.ui.util;

namespace zeal.ui.graph
{
    [TemplatePart(Name = "PART_connector", Type = typeof(FrameworkElement))]
    internal sealed partial class GraphPort : Control
    {
        private readonly GraphNode _node;
        private readonly TexturePort _port;
        private readonly bool _isInput;
        private readonly HashSet<GraphEdge> _edges;
        private Point _nodeRelativePos;

        public GraphNode node { get { return _node; } }
        public TexturePort port { get { return _port; } }
        public IEnumerable<GraphEdge> connectedEdges { get { return _edges; } }
        public string name { get { return port.portInfo.name; } }
        public bool isInput { get { return _isInput; } }
        public bool isOptional { get { return _isInput && ((TextureInputPortInfo) port.portInfo).isOptional; } }
        public string typeName { get { return port.type.ToString(); } }

        public GraphPort(GraphNode node, TexturePort port)
        {
            _node = node;
            _port = port;
            _isInput = port is TextureInputPort;
            _edges = new HashSet<GraphEdge>();
            DataContext = this;
            Loaded += onLoad;
            MouseLeftButtonDown += onClick;
            
            // Since both the edge and the fill use the same binding set, we can avoid creating a second set of bindings
            // Unfortunately, we lose the type safety I wasted so many hours implementing...
            Binding[] bindings = new Binding[]
            {
                Bindings.get<TextureType>(port, "type"),                         // type
                Bindings.get<bool>(this, "isInput"),                             // isInput
                Bindings.get(this, IsConnectedProperty),                         // isConnected
                Bindings.get(node, GraphNode.IsSelectedProperty),                // isNodeSelected
                Bindings.get(this, IsEdgeSelectedProperty),                      // isEdgeSelected
                Bindings.get(this, IsMouseOverProperty.of<bool>()),          // isHovered
                Bindings.get(this, ConnectionStateProperty),                     // dragState
                Bindings.constant(false),                                        // isDeletionTarget
            };

            Bindings.set(this, PortBorderProperty, Bindings.getMultiBindingUntyped(EdgeColorizer.instance.portBorderConverter, bindings));
            Bindings.set(this, PortFillProperty, Bindings.getMultiBindingUntyped(EdgeColorizer.instance.portFillConverter, bindings));
        }

        private void onLoad(object sender, RoutedEventArgs e)
        {
            FrameworkElement connector = this.getTemplatePart<FrameworkElement>("PART_connector");
            _nodeRelativePos = connector.TransformToAncestor(node).Transform(new Point(0, connector.RenderSize.Height / 2));
            Bindings.set(this, AnchorPointProperty,
                Bindings.multi(
                    Bindings.get(node, Canvas.LeftProperty.of<double>()),
                    Bindings.get(node, Canvas.TopProperty.of<double>()),
                    (x, y) => new Point(_nodeRelativePos.X + x, _nodeRelativePos.Y + y)));
        }


        private void onClick(object sender, MouseButtonEventArgs args)
        {
            node.editor.beginConnect(this, args);
            args.Handled = true;
        }

        public void addEdge(GraphEdge edge) { _edges.Add(edge); resetEdgeBindings(); }
        public void removeEdge(GraphEdge edge) { _edges.Remove(edge); resetEdgeBindings(); }
        private void resetEdgeBindings()
        {
            int n = _edges.Count;
            IsConnected = n > 0;
            Bindings.clear(this, IsEdgeSelectedProperty);
            if(n == 1)
                Bindings.set(this, IsEdgeSelectedProperty, _edges.First(), GraphEdge.IsActiveProperty);
            else if(n > 1)
                Bindings.set(this, IsEdgeSelectedProperty, Bindings.anyOf(_edges.Select(e => Bindings.get(e, GraphEdge.IsActiveProperty)).ToArray()));
        }

        public enum ConnectionStates
        {
            NONE,
            SOURCE,
            ERROR,
            VALID
        }
    }
}
